package com.qingcloud.fexecutor.base.utils;

import org.apache.ibatis.builder.xml.XMLMapperBuilder;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.mapping.ParameterMode;
import org.apache.ibatis.parsing.XNode;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.type.TypeHandlerRegistry;
import org.apache.xmlbeans.impl.common.ReaderInputStream;

import java.io.StringReader;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * @ClassName CustomMapperUtils
 * @Description TODO
 * @Author cwp
 * @Date 2021/6/9 17:05
 */
public class CustomMapperUtils {

    public static SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyyMMdd");

    // mybatis xml 文件起始字符串
    public static final String XML_START = """
            <?xml version="1.0" encoding="UTF-8"?>
            <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
            <mapper namespace="dynamicMapper">
            """;

    private static final String SELECT_SQL_START = "<select id=\"dynamicSql\"  parameterType=\"java.util.Map\">";
    private static final String SELECT_SQL_END = "</select>";

    private static final String INSERT_SQL_START = "<insert id=\"dynamicSql\"  parameterType=\"java.util.Map\">";
    private static final String INSERT_SQL_END = "</insert>";

    private static final String UPDATE_SQL_START = "<update id=\"dynamicSql\"  parameterType=\"java.util.Map\">";
    private static final String UPDATE_SQL_END = "</update>";

    private static final String DELETE_SQL_START = "<update id=\"dynamicSql\"  parameterType=\"java.util.Map\">";
    private static final String DELETE_SQL_END = "</update>";

    public static final String XML_END = "</mapper>";

    //mybatis sql语句ID
    public static final String SQL_ID = "dynamicSql";

    /**
     * @Description 解析含有mybatis 标签的查询sql,只需要传sql过来就行,方法自动拼接前后mybatis xml文件前后字符
     * @Author chengweiping
     * @Date 2021/6/10 9:37
     */
    public static String parseMybatisSql(String sql, Map<String, Object> paramMap) throws Exception {
        StringBuilder sqlBuilder = new StringBuilder();
        sqlBuilder.append(XML_START);
        if (sql.trim().toLowerCase().startsWith("select")) {
            sqlBuilder.append(SELECT_SQL_START).append(sql).append(SELECT_SQL_END);
        }
        if (sql.trim().toLowerCase().startsWith("insert")) {
            sqlBuilder.append(INSERT_SQL_START).append(sql).append(INSERT_SQL_END);
        }
        if (sql.trim().toLowerCase().startsWith("update")) {
            sqlBuilder.append(UPDATE_SQL_START).append(sql).append(UPDATE_SQL_END);
        }
        if (sql.trim().toLowerCase().startsWith("delete")) {
            sqlBuilder.append(DELETE_SQL_START).append(sql).append(DELETE_SQL_END);
        }
        sqlBuilder.append(XML_END);
        String sourceSql = sqlBuilder.toString();
        //解析
        Configuration configuration = new Configuration();
        XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(new ReaderInputStream(new StringReader(sourceSql), "UTF-8"), configuration, sourceSql, new HashMap<String, XNode>());
        xmlMapperBuilder.parse();
        MappedStatement mappedStatement = xmlMapperBuilder.getConfiguration().getMappedStatement(SQL_ID);
        BoundSql boundSql = mappedStatement.getBoundSql(paramMap);
        //替换参数值获取最终的sql
        return replaceSqlParamValue(configuration, boundSql);
    }

    /**
     * 替换sql参数值
     */
    private static String replaceSqlParamValue(Configuration configuration, BoundSql boundSql) {
        String sql = boundSql.getSql();
        //美化Sql
        sql.replaceAll("[\\s\n]+", " ");
        // 填充占位符， 把传参填进去，使用#｛｝、${} 一样的方式
        Object parameterObject = boundSql.getParameterObject();
        //参数映射列表，有入的（in） 有出的（O），后面只遍历传入的参数，并且把传入的参数赋值成我们代码里的值
        List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
        TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry();

        ArrayList<String> parmeters = new ArrayList<String>();
        if (parameterMappings != null) {
            MetaObject metaObject = parameterObject == null ? null : configuration.newMetaObject(parameterObject);
            for (int i = 0; i < parameterMappings.size(); i++) {
                ParameterMapping parameterMapping = parameterMappings.get(i);
                if (parameterMapping.getMode() != ParameterMode.OUT) {
                    //参数值
                    Object value;
                    String propertyName = parameterMapping.getProperty();
                    //获取参数名称
                    if (boundSql.hasAdditionalParameter(propertyName)) {
                        //获取参数值
                        value = boundSql.getAdditionalParameter(propertyName);
                    } else if (parameterObject == null) {
                        value = null;
                    } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
                        //如果是单个值则直接赋值
                        value = parameterObject;
                    } else {
                        value = metaObject == null ? null : metaObject.getValue(propertyName);
                    }
                    //由于传参类型多种多样，数值型的直接把value的值传参进去即可，如果是字符串的、日期的，要在前后加上单引号‘’，才能到sql里判读语句里使用
                    if (value instanceof Number) {
                        parmeters.add(String.valueOf(value));
                    } else {
                        StringBuilder builder = new StringBuilder();
                        builder.append("'");
                        if (value instanceof Date) {
                            builder.append(simpleDateFormat.format((Date) value));
                        } else if (value instanceof String) {
                            builder.append(value);
                        }
                        builder.append("'");
                        parmeters.add(builder.toString());
                    }
                }
            }
        }
        for (String value : parmeters) {
            sql = sql.replaceFirst("\\?", value);
        }
        return sql;

    }

    /**
     * 提取占位符参数变量名
     */
    public static Set<String> getParameterName(String command) {
        Pattern pattern = Pattern.compile("([$]|#)([{](.+?)[}])", Pattern.CASE_INSENSITIVE);
        Matcher matcher = pattern.matcher(command);
        Set<String> params = new HashSet<>();
        while (matcher.find()) {
            params.add(matcher.group(3));
        }
        return params;
    }

    public static void main(String[] args) throws Exception {
        String sql = "select * from test where name=#{input.value}";
        Map<String, Object> map = new HashMap<>();
        Map<String, Object> valueMap = new HashMap<>();
        valueMap.put("value", "1");

        map.put("input", valueMap);
        System.err.println(parseMybatisSql(sql, map));
    }
}